package com.reconcile.phone.system.service;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.reconcile.phone.common.constant.*;
import com.reconcile.phone.common.core.domain.dto.RoleDTO;
import com.reconcile.phone.common.core.domain.dto.UserOnlineDTO;
import com.reconcile.phone.common.core.domain.entity.SysUser;
import com.reconcile.phone.common.core.domain.event.LogininforEvent;
import com.reconcile.phone.common.core.domain.model.LoginUser;
import com.reconcile.phone.common.core.domain.model.XcxLoginUser;
import com.reconcile.phone.common.core.domain.wechat.WxAppLoginResp;
import com.reconcile.phone.common.core.domain.wechat.XcxLoginResp;
import com.reconcile.phone.common.enums.DeviceType;
import com.reconcile.phone.common.enums.LoginType;
import com.reconcile.phone.common.enums.UserStatus;
import com.reconcile.phone.common.enums.UserType;
import com.reconcile.phone.common.exception.ServiceException;
import com.reconcile.phone.common.exception.user.CaptchaException;
import com.reconcile.phone.common.exception.user.CaptchaExpireException;
import com.reconcile.phone.common.exception.user.UserException;
import com.reconcile.phone.common.helper.LoginHelper;
import com.reconcile.phone.common.utils.*;
import com.reconcile.phone.common.utils.redis.CacheUtils;
import com.reconcile.phone.common.utils.redis.RedisUtils;
import com.reconcile.phone.common.utils.spring.SpringUtils;
import com.reconcile.phone.im.IMUtil;
import com.reconcile.phone.system.mapper.SysUserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.util.*;
import java.util.function.Supplier;

/**
 * 登录校验方法
 *
 * @author Lion Li
 */
@RequiredArgsConstructor
@Slf4j
@Service
public class SysLoginService {

    private final SysUserMapper userMapper;
    private final ISysConfigService configService;
    private final ISysUserService userService;
    private final SysPermissionService permissionService;

    @Value("${user.password.maxRetryCount}")
    private Integer maxRetryCount;

    @Value("${user.password.lockTime}")
    private Integer lockTime;

    /**
     * 登录验证
     *
     * @param username 用户名
     * @param password 密码
     * @param code     验证码
     * @param uuid     唯一标识
     * @return 结果
     */
    public String login(String username, String password, String code, String uuid) {
        boolean captchaEnabled = configService.selectCaptchaEnabled();
        // 验证码开关
        if (captchaEnabled) {
            validateCaptcha(username, code, uuid);
        }
        // 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
        SysUser user = loadUserByUsername(username);
        checkLogin(LoginType.PASSWORD, username, () -> !BCrypt.checkpw(password, user.getPassword()));
        // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
        LoginUser loginUser = buildLoginUser(user);
        // 生成token
        LoginHelper.loginByDevice(loginUser, DeviceType.PC);

//        recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
//        recordLoginInfo(user.getUserId(), username);
        return StpUtil.getTokenValue();
    }

    public String smsLogin(String phonenumber, String smsCode) {
        // 通过手机号查找用户
        SysUser user = loadUserByPhonenumber(phonenumber);

        checkLogin(LoginType.SMS, user.getUserName(), () -> !validateSmsCode(phonenumber, smsCode));
        // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
        LoginUser loginUser = buildLoginUser(user);
        // 生成token
        LoginHelper.loginByDevice(loginUser, DeviceType.APP);

//        recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
//        recordLoginInfo(user.getUserId(), user.getUserName());
        return StpUtil.getTokenValue();
    }

    /**
     * 手机号登录(无账号则注册)
     *
     * @param phonenumber
     * @param smsCode
     * @return
     */
    public String smsLoginOrRegister(String phonenumber, String smsCode) {
        // 通过手机号查找用户
        SysUser user = findUserByPhonenumber(phonenumber);
        //没有用户创建用户
        if (ObjectUtil.isNull(user)) {
            checkSms(phonenumber, smsCode);
            SysUser sysUser = new SysUser();
            user = getSysUser(phonenumber, sysUser);
        }
        checkLogin(LoginType.SMS, user.getUserName(), () -> !validateSmsCode(phonenumber, smsCode));

        // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
        LoginUser loginUser = buildLoginUser(user);
        // 生成token
        LoginHelper.loginByDevice(loginUser, DeviceType.APP);

//        recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
//        recordLoginInfo(user.getUserId(), user.getUserName());
        return StpUtil.getTokenValue();
    }

    private SysUser getSysUser(String phonenumber, SysUser sysUser) {
        SysUser user;
        sysUser.setUserName(phonenumber);
        sysUser.setNickName(UserConstants.DEFAULT_NICK_NAME);
        sysUser.setPhonenumber(phonenumber);
        sysUser.setUserType(UserType.APP_USER.getUserType());
        boolean regFlag = userService.registerUser(sysUser);
        if (!regFlag) {
            throw new UserException("user.register.error");
        }
        user = findUserByPhonenumber(phonenumber);
        return user;
    }

    /**
     * 手机号一键登录
     *
     * @param accessToken 阿里云token
     * @return token
     */
    public String appOneClick(String accessToken) {
        String phonenumber = AppOneClickUtil.login(accessToken);
        // 通过手机号查找用户
        SysUser user = findUserByPhonenumber(phonenumber);

        //没有用户创建用户
        if (ObjectUtil.isNull(user)) {
            SysUser sysUser = new SysUser();
            user = getSysUser(phonenumber, sysUser);
        }

        // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
        LoginUser loginUser = buildLoginUser(user);
        // 生成token
        LoginHelper.loginByDevice(loginUser, DeviceType.APP);

//        recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
//        recordLoginInfo(user.getUserId(), user.getUserName());
        return StpUtil.getTokenValue();
    }


    public String emailLogin(String email, String emailCode) {
        // 通过手邮箱查找用户
        SysUser user = loadUserByEmail(email);

        checkLogin(LoginType.EMAIL, user.getUserName(), () -> !validateEmailCode(email, emailCode));
        // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
        LoginUser loginUser = buildLoginUser(user);
        // 生成token
        LoginHelper.loginByDevice(loginUser, DeviceType.APP);

//        recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
//        recordLoginInfo(user.getUserId(), user.getUserName());
        return StpUtil.getTokenValue();
    }

    public Map<String, Object> xcxLogin(String code) {
        //校验 appid + appsrcret + code 调用登录凭证校验接口 获取 session_key 与 openid
        XcxLoginResp xcxLoginResp = WxUtil.xcxLogin(code);
        //判断openid不能为空是否为空
        String openid = xcxLoginResp.getOpenid();
        log.info("小程序登录openid<<<<<<<<<<<<<<<" + openid);
        if (ObjectUtil.isNull(openid)) {
            throw new ServiceException("openid.is.empty");
        }

        //查询用户信息是否存在
        LambdaQueryWrapper<SysUser> sysUserLambdaQueryWrapper = new LambdaQueryWrapper<SysUser>()
                .eq(SysUser::getOpenid, openid).select(SysUser::getUserId, SysUser::getPhonenumber);
        SysUser checkUser = userMapper.selectOne(sysUserLambdaQueryWrapper);

        if (ObjectUtil.isNull(checkUser)) {
            //未绑定openid
            CacheUtils.put(CacheNames.XCX_LOGIN_RESP, code, xcxLoginResp);
            throw new ServiceException("openid.is.not.bind", HttpStatus.UNBOUND);
        }

        //查询用户信息
        SysUser user = findUserByPhonenumber(checkUser.getPhonenumber());
        //构建登录实体
        LoginUser loginUser = buildLoginUser(user);
        XcxLoginUser userInfo = BeanUtil.toBean(loginUser, XcxLoginUser.class);
        userInfo.setOpenid(user.getOpenid());
        // 生成token
        LoginHelper.loginByDevice(userInfo, DeviceType.XCX);
        //记录登陆信息
//        this.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
//        recordLoginInfo(user.getUserId(), user.getUserName());
        //响应
        Map<String, Object> ajax = new HashMap<>();
        ajax.put(Constants.TOKEN, StpUtil.getTokenValue());
        ajax.put("phoneNumber", user.getPhonenumber());
        ajax.put("secretKey", user.getSecretKey());
        //修改临时有效期
//        LoginHelper.updateActivityToken(StpUtil.getTokenValue(), 1800L);
        return ajax;
    }

    /**
     * 第一次小程序登陆
     *
     * @param loginCode 小程序登陆凭证
     * @param phoneCode 小程序获取手机号凭证
     * @return token
     */
    public Map<String, Object> xcxFirstLogin(String loginCode, String phoneCode) {
        //获取用户信息
        XcxLoginResp xcxLoginResp = CacheUtils.get(CacheNames.XCX_LOGIN_RESP, loginCode);
        JudgeUtils.conditionException(ObjectUtil.isNull(xcxLoginResp)).errMsg("请在5分钟内授权");
        //获取微信手机号
        String phoneNumber = WxUtil.xcxGetPhoneNumber(phoneCode);
        // 通过手机号查找用户
        SysUser user = findUserByPhonenumber(phoneNumber);

        //没有用户创建用户
        if (ObjectUtil.isNull(user)) {
            SysUser sysUser = new SysUser();
            sysUser.setUserName(phoneNumber);
            sysUser.setNickName(UserConstants.DEFAULT_NICK_NAME);
            sysUser.setPhonenumber(phoneNumber);
            sysUser.setMyInvitationCode(generateUniqueInvitationCode());
            sysUser.setUserType(UserType.APP_USER.getUserType());
            sysUser.setOpenid(Objects.requireNonNull(xcxLoginResp).getOpenid());
            sysUser.setUnionid(xcxLoginResp.getUnionid());
            boolean regFlag = userService.registerUser(sysUser);
            if (!regFlag) {
                throw new UserException("user.register.error");
            }
            user = findUserByPhonenumber(phoneNumber);
        } else {
            //之前登陆过, 绑定微信唯一标识
            String secretKey = IdUtil.simpleUUID();
            Boolean bindResult = userService.bindWxInfo(user.getUserId(), xcxLoginResp.getOpenid(), xcxLoginResp.getUnionid(), secretKey);
            if (BooleanUtil.isFalse(bindResult)) {
                throw new ServiceException("此手机号已和其他微信绑定");
            }
            user.setSecretKey(secretKey);
        }
        CacheUtils.evict(CacheNames.XCX_LOGIN_RESP, loginCode);

        //构建登录实体
        LoginUser loginUser = buildLoginUser(user);
        XcxLoginUser userInfo = BeanUtil.toBean(loginUser, XcxLoginUser.class);
        userInfo.setOpenid(user.getOpenid());
        // 生成token
        LoginHelper.loginByDevice(userInfo, DeviceType.XCX);
        //记录登陆信息
//        this.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
//        recordLoginInfo(user.getUserId(), user.getUserName());
        //响应
        Map<String, Object> ajax = new HashMap<>();
        ajax.put(Constants.TOKEN, StpUtil.getTokenValue());
        ajax.put("phoneNumber", user.getPhonenumber());
        ajax.put("secretKey", user.getSecretKey());
        //修改临时有效期
//        LoginHelper.updateActivityToken(StpUtil.getTokenValue(), 1800L);
        return ajax;
    }

    /**
     * 邀请码登录
     *
     * @param code
     * @param invitationCode
     * @return
     */
    public Map<String, Object> yqmLogin(String code, String invitationCode) {
        //根据邀请码获取此用户信息
        SysUser sysUser = userMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getMyInvitationCode, invitationCode));
        if (ObjectUtil.isNull(sysUser)) {
            throw new ServiceException("邀请码错误");
        }
        //校验 appid + appsrcret + code 调用登录凭证校验接口 获取 session_key 与 openid
        XcxLoginResp xcxLoginResp = WxUtil.xcxLogin(code);
        //判断openid不能为空是否为空
        String openid = xcxLoginResp.getOpenid();
        if (ObjectUtil.isNull(openid)) {
            throw new ServiceException("openid.is.empty");
        }

        //查询用户信息是否存在  修改
        LambdaQueryWrapper<SysUser> sysUserLambdaQueryWrapper = new LambdaQueryWrapper<SysUser>()
                .eq(SysUser::getOpenid, openid).select(SysUser::getUserId, SysUser::getPhonenumber, SysUser::getInvitationCode, SysUser::getMyInvitationCode);
        SysUser checkUser = userMapper.selectOne(sysUserLambdaQueryWrapper);

        if (ObjectUtil.isNull(checkUser)) {
            //未绑定openid  修改
            CacheUtils.put(CacheNames.XCX_LOGIN_RESP, code, xcxLoginResp);
            throw new ServiceException("openid.is.not.bind", HttpStatus.UNBOUND);
        }
        //校验不能自己邀请自己
        if (invitationCode.equals(checkUser.getMyInvitationCode())) {
            throw new ServiceException("自己不能邀请自己！", HttpStatus.CONFLICT);
        }
        //校验是否以被邀请
        if (StringUtils.isNotEmpty(checkUser.getInvitationCode())) {
            throw new ServiceException("该账号已被邀请过！", HttpStatus.CONFLICT);
        }
        //查询用户信息
        SysUser user = findUserByPhonenumber(checkUser.getPhonenumber());
        //构建登录实体
        LoginUser loginUser = buildLoginUser(user);
        XcxLoginUser userInfo = BeanUtil.toBean(loginUser, XcxLoginUser.class);
        userInfo.setOpenid(user.getOpenid());
        // 生成token
        LoginHelper.loginByDevice(userInfo, DeviceType.XCX);
        //记录登陆信息
//        this.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
//        recordLoginInfo(user.getUserId(), user.getUserName());
        //响应
        Map<String, Object> ajax = new HashMap<>();
        ajax.put(Constants.TOKEN, StpUtil.getTokenValue());
        ajax.put("phoneNumber", user.getPhonenumber());
        ajax.put("secretKey", user.getSecretKey());
        //添加用户邀请码
        checkUser.setInvitationCode(invitationCode);
        int result = userMapper.updateById(checkUser);
        if (result < 1) {
            throw new ServiceException("邀请失败！");
        }
        //修改临时有效期
//        LoginHelper.updateActivityToken(StpUtil.getTokenValue(), 1800L);
        return ajax;
    }

    /**
     * 邀请码第一次小程序登录,绑定手机号
     *
     * @param loginCode      小程序登陆凭证
     * @param phoneCode      小程序获取手机号凭证
     * @param invitationCode 扫描人邀请码
     * @return token
     */
    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> yqmFirstLogin(String loginCode, String phoneCode, String invitationCode) {
        //获取用户信息
        XcxLoginResp xcxLoginResp = CacheUtils.get(CacheNames.XCX_LOGIN_RESP, loginCode);
        JudgeUtils.conditionException(ObjectUtil.isNull(xcxLoginResp)).errMsg("请在5分钟内授权");
        //获取微信手机号
        String phoneNumber = WxUtil.xcxGetPhoneNumber(phoneCode);
        // 通过手机号查找用户
        SysUser user = findUserByPhonenumber(phoneNumber);

        //没有用户创建用户
        if (ObjectUtil.isNull(user)) {
            SysUser sysUser = new SysUser();
            sysUser.setUserName(phoneNumber);
            sysUser.setNickName(UserConstants.DEFAULT_NICK_NAME);
            sysUser.setPhonenumber(phoneNumber);
            sysUser.setMyInvitationCode(generateUniqueInvitationCode());
            sysUser.setInvitationCode(invitationCode);
            sysUser.setUserType(UserType.APP_USER.getUserType());
            sysUser.setOpenid(Objects.requireNonNull(xcxLoginResp).getOpenid());
            sysUser.setUnionid(xcxLoginResp.getUnionid());
            boolean regFlag = userService.registerUser(sysUser);
            if (!regFlag) {
                throw new UserException("user.register.error");
            }
            user = findUserByPhonenumber(phoneNumber);
        } else {
            //之前登陆过, 绑定微信唯一标识
            String secretKey = IdUtil.simpleUUID();
            Boolean bindResult = userService.bindWxInfo(user.getUserId(), xcxLoginResp.getOpenid(), xcxLoginResp.getUnionid(), secretKey);
            if (BooleanUtil.isFalse(bindResult)) {
                throw new ServiceException("此手机号已和其他微信绑定");
            }
            user.setSecretKey(secretKey);
        }
        CacheUtils.evict(CacheNames.XCX_LOGIN_RESP, loginCode);

        //构建登录实体
        LoginUser loginUser = buildLoginUser(user);
        XcxLoginUser userInfo = BeanUtil.toBean(loginUser, XcxLoginUser.class);
        userInfo.setOpenid(user.getOpenid());
        // 生成token
        LoginHelper.loginByDevice(userInfo, DeviceType.XCX);
        //记录登陆信息
//        this.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
//        recordLoginInfo(user.getUserId(), user.getUserName());
        //响应
        Map<String, Object> ajax = new HashMap<>();
        ajax.put(Constants.TOKEN, StpUtil.getTokenValue());
        ajax.put("phoneNumber", user.getPhonenumber());
        ajax.put("secretKey", user.getSecretKey());
        //校验此邀请码是否成功邀请了12个人
        List<SysUser> userList = userMapper.selectList(new LambdaQueryWrapper<SysUser>().eq(SysUser::getInvitationCode, invitationCode));
        if (userList.size() > 11) {
            //根据邀请码查询用户信息
            SysUser sysUser = userMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getMyInvitationCode, invitationCode));
            if (ObjectUtil.isNull(sysUser)) {
                throw new ServiceException("邀请码错误！请联系管理员");
            }
            //修改成分店
            sysUser.setIsBranch("1");
            int result = userMapper.updateById(sysUser);
            if (result < 1) {
                throw new ServiceException("注册失败！");
            }
        }
        //修改临时有效期
//        LoginHelper.updateActivityToken(StpUtil.getTokenValue(), 1800L);
        return ajax;
    }

    /**
     * 退出登录
     */
    public void logout() {
        try {
            LoginUser loginUser = LoginHelper.getLoginUser();
//            recordLogininfor(loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
        } catch (NotLoginException ignored) {
        } finally {
            try {
                StpUtil.logout();
            } catch (NotLoginException ignored) {
            }
        }
    }

    /**
     * 记录登录信息
     *
     * @param username 用户名
     * @param status   状态
     * @param message  消息内容
     */
    private void recordLogininfor(String username, String status, String message) {
        LogininforEvent logininforEvent = new LogininforEvent();
        logininforEvent.setUsername(username);
        logininforEvent.setStatus(status);
        logininforEvent.setMessage(message);
        logininforEvent.setRequest(ServletUtils.getRequest());
        SpringUtils.context().publishEvent(logininforEvent);
    }

    /**
     * 校验短信验证码
     */
    public boolean validateSmsCode(String phonenumber, String smsCode) {
        if (this.captchaAnon(smsCode)) {
            String code = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + phonenumber);
            if (StringUtils.isBlank(code)) {
//                recordLogininfor(phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
                throw new CaptchaExpireException();
            }
            return code.equals(smsCode);
        }
        return true;
    }

    /**
     * 校验验证码
     *
     * @param phonenumber 手机号
     * @param smsCode     验证码
     */
    public void checkSms(String phonenumber, String smsCode) {
        boolean checkSms = validateSmsCode(phonenumber, smsCode);
        if (!checkSms) {
            throw new ServiceException("验证码错误");
        }
    }

    /**
     * 验证码后门放行
     *
     * @param code 验证码
     * @return 判断 true: 不放行   false:放行
     */
    private boolean captchaAnon(String code) {
        try {
            SpringUtils.getBean("captchaBackdoor");
            if (code.equals("6666")) {
                return false;
            }
        } catch (BeansException e) {
            //无bean
        }
        return true;
    }

    /**
     * 校验邮箱验证码
     */
    private boolean validateEmailCode(String email, String emailCode) {
        String code = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + email);
        if (StringUtils.isBlank(code)) {
//            recordLogininfor(email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
            throw new CaptchaExpireException();
        }
        return code.equals(emailCode);
    }

    /**
     * 校验验证码
     *
     * @param username 用户名
     * @param code     验证码
     * @param uuid     唯一标识
     */
    public void validateCaptcha(String username, String code, String uuid) {
        String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
        String captcha = RedisUtils.getCacheObject(verifyKey);
        RedisUtils.deleteObject(verifyKey);
        if (captcha == null) {
//            recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
            throw new CaptchaExpireException();
        }
        if (!code.equalsIgnoreCase(captcha)) {
//            recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
            throw new CaptchaException();
        }
    }

    private SysUser loadUserByUsername(String username) {
        SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
                .select(SysUser::getUserName, SysUser::getStatus)
                .eq(SysUser::getUserName, username));
        if (ObjectUtil.isNull(user)) {
            log.info("登录用户：{} 不存在.", username);
            throw new UserException("user.not.exists", username);
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
            log.info("登录用户：{} 已被停用.", username);
            throw new UserException("user.blocked", username);
        }
        return userMapper.selectUserByUserName(username);
    }

    private SysUser loadUserByPhonenumber(String phonenumber) {
        SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
                .select(SysUser::getPhonenumber, SysUser::getStatus)
                .eq(SysUser::getPhonenumber, phonenumber));
        if (ObjectUtil.isNull(user)) {
            log.info("登录用户：{} 不存在.", phonenumber);
            throw new UserException("user.not.exists", phonenumber);
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
            log.info("登录用户：{} 已被停用.", phonenumber);
            throw new UserException("user.blocked", phonenumber);
        }
        return userMapper.selectUserByPhonenumber(phonenumber);
    }

    private SysUser loadUserByEmail(String email) {
        SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
                .select(SysUser::getPhonenumber, SysUser::getStatus)
                .eq(SysUser::getEmail, email));
        if (ObjectUtil.isNull(user)) {
            log.info("登录用户：{} 不存在.", email);
            throw new UserException("user.not.exists", email);
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
            log.info("登录用户：{} 已被停用.", email);
            throw new UserException("user.blocked", email);
        }
        return userMapper.selectUserByEmail(email);
    }

    private SysUser loadUserByOpenid(String openid) {
        // 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
        // todo 自行实现 userService.selectUserByOpenid(openid);
        SysUser user = new SysUser();
        if (ObjectUtil.isNull(user)) {
            log.info("登录用户：{} 不存在.", openid);
            // todo 用户不存在 业务逻辑自行实现
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
            log.info("登录用户：{} 已被停用.", openid);
            // todo 用户已被停用 业务逻辑自行实现
        }
        return user;
    }

    /**
     * 构建登录用户
     */
    private LoginUser buildLoginUser(SysUser user) {
        LoginUser loginUser = new LoginUser();
        loginUser.setUserId(user.getUserId());
        loginUser.setDeptId(user.getDeptId());
        loginUser.setUsername(user.getUserName());
        loginUser.setUserType(user.getUserType());
        loginUser.setMenuPermission(permissionService.getMenuPermission(user));
        loginUser.setRolePermission(permissionService.getRolePermission(user));
        loginUser.setDeptName(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptName());
        List<RoleDTO> roles = BeanUtil.copyToList(user.getRoles(), RoleDTO.class);
        loginUser.setRoles(roles);
        return loginUser;
    }

    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId, String username) {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLoginIp(ServletUtils.getClientIP());
        sysUser.setLoginDate(DateUtils.getNowDate());
        sysUser.setUpdateBy(username);
        userMapper.updateById(sysUser);
        this.sendCustomerServiceMessage(userId);
    }

    /**
     * 登录校验
     */
    private void checkLogin(LoginType loginType, String username, Supplier<Boolean> supplier) {
        String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
        String loginFail = Constants.LOGIN_FAIL;

        // 获取用户登录错误次数，默认为0 (可自定义限制策略 例如: key + username + ip)
        int errorNumber = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(errorKey), 0);
        // 锁定时间内登录 则踢出
        if (errorNumber >= maxRetryCount) {
//            recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
            throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
        }

        if (supplier.get()) {
            // 错误次数递增
            errorNumber++;
            RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
            // 达到规定错误次数 则锁定登录
            if (errorNumber >= maxRetryCount) {
//                recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
                throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
            } else {
                // 未达到规定错误次数
//                recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
                throw new UserException(loginType.getRetryLimitCount(), errorNumber);
            }
        }

        // 登录成功 清空错误次数
        RedisUtils.deleteObject(errorKey);
    }


    private SysUser findUserByPhonenumber(String phonenumber) {
        SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
                .select(SysUser::getPhonenumber, SysUser::getStatus)
                .eq(SysUser::getPhonenumber, phonenumber));
        if (ObjectUtil.isNull(user)) {
            return user;
        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
            log.info("登录用户：{} 已被停用.", phonenumber);
            throw new UserException("user.blocked", phonenumber);
        }
        return userMapper.selectUserByPhonenumber(phonenumber);
    }

    /**
     * 获取在线用户列表
     *
     * @return
     */
    public List<UserOnlineDTO> getUserOnlineList() {
        // 获取所有未过期的 token
        List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
        List<UserOnlineDTO> userOnlineDTOList = new ArrayList<>();
        for (String key : keys) {
            String token = StringUtils.substringAfterLast(key, ":");
            // 如果已经过期则跳过
            if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < -1) {
                continue;
            }
            userOnlineDTOList.add(RedisUtils.getCacheObject(CacheConstants.ONLINE_TOKEN_KEY + token));
        }
        //移除空的在线列表
        userOnlineDTOList.removeAll(Collections.singleton(null));

        //根据手机号获取在线用户列表
/*   userOnlineDTOList = StreamUtils.filter(userOnlineDTOList, userOnline ->
              StringUtils.equals(userName, userOnline.getUserName()));

    if(userOnlineDTOList.isEmpty()){
      return false;
    }*/
        return userOnlineDTOList;
    }

    /**
     * 获取发布招工人（职位）手机号
     *
     * @param userId
     * @return
     */
    public String jobPhonenumber(Long userId) {
        return userMapper.jobPhonenumber(userId);
    }


    /**
     * app微信登录
     *
     * @param code app微信登陆code
     * @return token
     */
    public String appLogin(String code) {
        //登录
        WxAppLoginResp wxAppLoginResp = WxUtil.appLogin(code);
        //判断unionid是否为空
        String unionid = wxAppLoginResp.getUnionid();
        if (ObjectUtil.isNull(unionid)) {
            throw new ServiceException("unionid.is.empty");
        }

        //查询用户信息是否存在
        LambdaQueryWrapper<SysUser> sysUserLambdaQueryWrapper = new LambdaQueryWrapper<SysUser>()
                .eq(SysUser::getUnionid, unionid).select(SysUser::getUserId, SysUser::getPhonenumber);
        SysUser checkUser = userMapper.selectOne(sysUserLambdaQueryWrapper);

        if (ObjectUtil.isNull(checkUser)) {
            //未绑定unionid
            CacheUtils.put(CacheNames.APP_WX_LOGIN_RESP, code, wxAppLoginResp);
            throw new ServiceException("unionid.is.not.bind", HttpStatus.UNBOUND);
        }

        //查询用户信息
        SysUser user = findUserByPhonenumber(checkUser.getPhonenumber());
        //构建登录实体
        LoginUser loginUser = buildLoginUser(user);
        XcxLoginUser userInfo = BeanUtil.toBean(loginUser, XcxLoginUser.class);
        userInfo.setOpenid(user.getOpenid());
        // 生成token
        LoginHelper.loginByDevice(userInfo, DeviceType.APP);
        //记录登陆信息
//        this.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
//        recordLoginInfo(user.getUserId(), user.getUserName());
        //响应
        return StpUtil.getTokenValue();
    }

    /**
     * 第一次app登录,绑定手机号
     *
     * @param code        微信登陆凭证
     * @param phoneNumber 手机号
     * @return token
     */
    public String wxAppFirstLogin(String code, String phoneNumber, String smsCode) {
        //校验短信验证码
        checkSms(phoneNumber, smsCode);
        //获取用户信息
        WxAppLoginResp wxAppLoginResp = CacheUtils.get(CacheNames.APP_WX_LOGIN_RESP, code);
        JudgeUtils.conditionException(ObjectUtil.isNull(wxAppLoginResp)).errMsg("请在5分钟内授权");
        // 通过手机号查找用户
        SysUser user = findUserByPhonenumber(phoneNumber);

        //没有用户创建用户
        if (ObjectUtil.isNull(user)) {
            SysUser sysUser = new SysUser();
            sysUser.setUserName(phoneNumber);
            sysUser.setNickName(UserConstants.DEFAULT_NICK_NAME);
            sysUser.setPhonenumber(phoneNumber);
            sysUser.setUserType(UserType.APP_USER.getUserType());
            sysUser.setOpenid(Objects.requireNonNull(wxAppLoginResp).getOpenid());
            sysUser.setUnionid(wxAppLoginResp.getUnionid());
            boolean regFlag = userService.registerUser(sysUser);
            if (!regFlag) {
                throw new UserException("user.register.error");
            }
            user = findUserByPhonenumber(phoneNumber);
        } else {
            //之前登陆过, 绑定微信唯一标识
            String secretKey = IdUtil.simpleUUID();
            Boolean bindResult =
                    userService.bindWxInfo(user.getUserId(), wxAppLoginResp.getOpenid(), wxAppLoginResp.getUnionid(), secretKey);
            if (BooleanUtil.isFalse(bindResult)) {
                throw new ServiceException("此手机号已和其他微信绑定");
            }
            user.setSecretKey(secretKey);
        }
        CacheUtils.evict(CacheNames.APP_WX_LOGIN_RESP, code);

        //构建登录实体
        LoginUser loginUser = buildLoginUser(user);
        XcxLoginUser userInfo = BeanUtil.toBean(loginUser, XcxLoginUser.class);
        userInfo.setOpenid(user.getOpenid());
        // 生成token
        LoginHelper.loginByDevice(userInfo, DeviceType.APP);
        //记录登陆信息
//        this.recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
//        recordLoginInfo(user.getUserId(), user.getUserName());

        return StpUtil.getTokenValue();
    }

    private void sendCustomerServiceMessage(Long userId) {
        //发送客服消息
        IMUtil.sendTextMessageToUser(Collections.singletonList(userId), IMConstants.FROM_LABOR_ADMIN, "您好,请问有什么可以帮助您",
                UserConstants.WORKER);
        IMUtil.sendTextMessageToUser(Collections.singletonList(userId), IMConstants.FROM_LABOR_ADMIN, "您好,请问有什么可以帮助您",
                UserConstants.EMPLOYER);
    }

    /**
     * 生成唯一的邀请码
     *
     * @return 唯一的邀请码
     */
    private String generateUniqueInvitationCode() {
        String invitationCode;
        do {
            invitationCode = RandomUtil.randomNumbers(8);
        } while (userMapper.exists(new LambdaQueryWrapper<SysUser>().eq(SysUser::getMyInvitationCode, invitationCode)));
        return invitationCode;
    }

}
